/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.evaluation;

import cn.ac.nercis.pes.common.constant.evaluation.SecurityThreatMode;
import cn.ac.nercis.pes.common.constant.knowledge.ThreatType;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.common.ResultCode;
import cn.ac.nercis.pes.model.dto.evaluation.prepare.SecurityThreatDTO;
import cn.ac.nercis.pes.repository.dal.evaluation.SecurityThreatRepository;
import cn.ac.nercis.pes.repository.dal.knowledge.KmSecurityThreatRepository;
import cn.ac.nercis.pes.repository.dal.knowledge.KmTargetRepository;
import cn.ac.nercis.pes.repository.mapper.evaluation.SecurityThreatMapper;
import cn.ac.nercis.pes.repository.model.evaluation.SecurityThreatDO;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 安全威胁信息服务类
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public class SecurityThreatService {
    private final static String KM_ORDER_FIELD = "sort";

    private final KmSecurityThreatRepository kmSecurityThreatRepository;
    private final SecurityThreatRepository securityThreatRepository;
    private final KmTargetRepository kmTargetRepository;

    @Autowired
    public SecurityThreatService(KmSecurityThreatRepository kmSecurityThreatRepository, SecurityThreatRepository securityThreatRepository,
                                 KmTargetRepository kmTargetRepository) {
        this.kmSecurityThreatRepository = kmSecurityThreatRepository;
        this.securityThreatRepository = securityThreatRepository;
        this.kmTargetRepository = kmTargetRepository;
    }

    public List<SecurityThreatDTO> findSecurityThreat(String systemId){
        List<SecurityThreatDO> securityThreatDOS = this.securityThreatRepository.findAllBySystemId(systemId);
        return ThreatType.findThreatTypeNames()
                .stream()
                .flatMap(type->securityThreatDOS
                        .stream()
                        .filter(st-> StringUtils.equals(st.getType(),type))
                        .sorted(Comparator.comparingInt(SecurityThreatDO::getSort))
                        .map(SecurityThreatMapper.INSTANCE::toThreatDTO))
                .collect(Collectors.toList());
    }

    public List<SecurityThreatDTO> findSecurityThreatByCustom(String systemId){
        List<SecurityThreatDO> securityThreatDOS = this.securityThreatRepository.findAllBySystemId(systemId);
        return ThreatType.findThreatTypeNames()
                .stream()
                .flatMap(type->securityThreatDOS
                        .stream()
                        .filter(st-> StringUtils.equals(st.getType(),type)
                                && st.getMode() == SecurityThreatMode.CUSTOM)
                        .sorted(Comparator.comparingInt(SecurityThreatDO::getSort))
                        .map(SecurityThreatMapper.INSTANCE::toThreatDTO))
                .collect(Collectors.toList());
    }

    public Result<List<SecurityThreatDTO>> findSecurityThreats(@NonNull String systemId) {
        List<SecurityThreatDTO> securityThreats = securityThreatRepository.findAllBySystemId(systemId)
                .stream()
                .sorted(Comparator.comparingInt(SecurityThreatDO::getSort))
                .map(SecurityThreatMapper.INSTANCE::toThreatDTO)
                .collect(Collectors.toList());
        return Result.success(securityThreats);
    }

    public void initSecurityThreat(String systemId){
        List<SecurityThreatDO> defaultThreats = this.kmSecurityThreatRepository.findAll(Sort.by(Sort.Direction.ASC,KM_ORDER_FIELD))
                .stream()
                .map(SecurityThreatMapper.INSTANCE::toThreatDO)
                .peek(et->{
                    et.setSystemId(systemId);
                    et.setMode(SecurityThreatMode.KNOWLEDGE);
                    et.setCreateTime(LocalDateTime.now());
                })
                .collect(Collectors.toList());
        this.securityThreatRepository.saveAll(defaultThreats);
    }

    public Result<SecurityThreatDTO> saveSecurityThreat(SecurityThreatDTO securityThreat){
        if(StringUtils.isBlank(securityThreat.getCode())){
            return Result.failed("威胁序号不能为空");
        }
        if(Objects.isNull(ThreatType.fromName(securityThreat.getType()))){
            return Result.failed(StringUtils.isBlank(securityThreat.getType())?"威胁类别不能为空":"威胁类别无效");
        }
        if(StringUtils.isBlank(securityThreat.getName())){
            return Result.failed("威胁分类不能为空");
        }
        try{
            if(StringUtils.isEmpty(securityThreat.getId())){
                return create(securityThreat);
            }else{
                return modify(securityThreat);
            }
        }catch (Exception ex){
            log.error("保存安全威胁信息出错：",ex);
            return Result.failed();
        }
    }

    public Result<String> batchSaveSecurityThreat(List<SecurityThreatDTO> data){
        Result<String> result = Result.success();
        for(SecurityThreatDTO item : data){
            Result<SecurityThreatDTO> itemResult = saveSecurityThreat(item);
            if(!itemResult.isSuccess()){
                result = Result.failed(ResultCode.FAIL);
                break;
            }
        }
        return result;
    }

    public Result<String> deleteSecurityThreat(String id){
        SecurityThreatDO entity = this.securityThreatRepository.findById(id).orElse(null);
        if(Objects.isNull(entity)){
            return Result.failed(ResultCode.NOT_FOUND);
        }
        if(entity.getMode() == SecurityThreatMode.KNOWLEDGE){
            return Result.failed("知识库中的安全威胁不允许删除");
        }
        try{
            return Optional.of(entity)
                    .map(st -> {
                        securityThreatRepository.delete(st);
                        return st;
                    })
                    .map(SecurityThreatDO::getId)
                    .map(Result::success)
                    .orElse(Result.failed());
        }catch (Exception ex){
            log.error("删除安全威胁信息出错：",ex);
            return Result.failed();
        }
    }

    public Result<String> batchDeleteSecurityThreat(Set<String> ids){
        Result<String> result = Result.success();
        for(String id : ids){
            Result<String> itemResult = deleteSecurityThreat(id);
            if(!itemResult.isSuccess()){
                result = Result.failed(ResultCode.FAIL);
                break;
            }
        }
        return result;
    }

    private Result<SecurityThreatDTO> create(SecurityThreatDTO securityThreat){
        List<SecurityThreatDO> source = this.securityThreatRepository.findAllBySystemId(securityThreat.getSystemId());
        if (source.stream().anyMatch(st -> StringUtils.equals(st.getCode(), securityThreat.getCode()))) {
            return Result.failed("安全威胁序号已存在");
        }
        int lastSort = source.stream()
                .filter(st->StringUtils.equals(st.getType(),securityThreat.getType()))
                .max(Comparator.comparingInt(SecurityThreatDO::getSort))
                .map(SecurityThreatDO::getSort)
                .orElse(1);
        return Optional.of(SecurityThreatMapper.INSTANCE.toThreatDO(securityThreat))
                .map(st->{
                    st.setCreateTime(LocalDateTime.now());
                    st.setSort(lastSort);
                    st.setThreadId("");
                    st.setMode(SecurityThreatMode.CUSTOM);
                    return st;
                })
                .map(securityThreatRepository::save)
                .map(SecurityThreatMapper.INSTANCE::toThreatDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private Result<SecurityThreatDTO> modify(SecurityThreatDTO securityThreat){
        List<SecurityThreatDO> source = this.securityThreatRepository.findAllBySystemId(securityThreat.getSystemId());
        SecurityThreatDO entity = source.stream().filter(st->StringUtils.equals(st.getId(),securityThreat.getId())).findFirst().orElse(null);
        if(Objects.isNull(entity)){
            return Result.failed(ResultCode.NOT_FOUND);
        }
        if (source.stream().anyMatch(st -> !StringUtils.equals(st.getId(), entity.getId())
                && StringUtils.equals(st.getCode(),securityThreat.getCode()))) {
            return Result.failed("安全威胁序号已存在");
        }
        return Optional.of(entity)
                .map(st->{
                    st.setCode(securityThreat.getCode());
                    st.setType(securityThreat.getType());
                    st.setName(securityThreat.getName());
                    st.setModifyTime(LocalDateTime.now());
                    return st;
                })
                .map(securityThreatRepository::save)
                .map(SecurityThreatMapper.INSTANCE::toThreatDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

}